package hyl.core.safe;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import hyl.core.MyCharset;
import hyl.core.MyFun;
import hyl.core.fun.MyByte;
import hyl.core.info.MyBase64;
import hyl.core.io.MyFile;

import java.io.File;
import java.io.FileInputStream;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Date;
import java.util.Enumeration;

/**
 * 
 * 
 * 
 * 非对称加密的用途<br>
 * 对于需要保密的数据, 公钥加密数据,私钥解密数据 <br>
 * 对于需要确认发布人身份的公开数据, 用私钥加密数据,所有持有公钥的人都可以解密数据<br>
 * <br>
 * 本对象结构<br>
 * <br>
 * 1) 创建非对称密钥<br>
 * 2) 载入和保存密钥<br>
 * 3) 载入证书<br>
 * 4) 验证证书<br>
 * 5) 读取证书属性<br>
 * 6) 读取公钥和私钥<br>
 * 7) 加密文档和数据 (公共函数部分)
 * 
 * <br>
 * 
 * 
 * 
 * @author dayou
 */
public class MyCert extends MySecret {
	// public static final String RSA = "RSA";

	protected PrivateKey _prikey;// 私钥
	protected PublicKey _pubkey;// 公钥
	protected int _keysize = 1024;

	/**
	 * Java密钥库(Java 密钥库，JKS)KEY_STORE
	 */
	/**
	 * <p>
	 * className: KS
	 * </p>
	 * <p>
	 * Description: 载入文件类型
	 * </p>
	 * 
	 * @author: zdy
	 * @date: 2018年9月30日 下午10:35:13
	 * @copyright: 2018 da you zou Inc. All rights reserved.
	 */
	public final String JKS = "JKS", // 【Java Keystore】密钥库的Java实现版本
			// PKCS#1：RSA加密标准。PKCS#1定义了RSA公钥函数的基本格式标准，特别是数字签名。它定义了数字签名如何计算，包括待签名数据和签名本身的格式；它也定义了PSA公/私钥的语法。
			PKCS1 = "PKCS1",
			// PKCS#8：私钥信息语法标准。PKCS#8定义了私钥信息语法和加密私钥语法，其中私钥加密使用了PKCS#5标准。
			PKCS8 = "PKCS8",
			// PKCS#12：个人信息交换语法标准。
			// PKCS#12定义了个人身份信息（包括私钥、证书、各种秘密和扩展字段）的格式。
			// PKCS#12有助于传输证书及对应的私钥，于是用户可以在不同设备间移动他们的个人身份信息。
			// .p12/.pfx 描述 : 【PKCS #12】个人信息交换语法标准
			// 特点 : 1、包含私钥、公钥及其证书 2、密钥库和私钥用相同密码进行保护
			PKCS12 = "PKCS12",
			// X509 是常见通用的证书格式。X.509证书也允许很大的灵活性，因为它为大多数字段提供了多种编码方案.
			X509 = "X509";
	/*
	 * X.509 DER 编码(ASCII)的后缀是： .DER .CER .CRT<br> X.509 PAM 编码(Base64)的后缀是： .PEM
	 * .CER .CRT<br> .cer/.crt是用于存放证书，它是2进制形式存放的，不含私钥。<br>
	 * .pem跟crt/cer的区别是它以Ascii来表示。<br> pfx/p12用于存放个人证书/私钥，他通常包含保护密码，2进制方式<br>
	 * p10是证书请求
	 */
	/**
	 * 文件读取缓冲区大小
	 */
	@SuppressWarnings("unused")
	private static final int CACHE_SIZE = 2048;
	/**
	 * 最大文件加密块
	 */
	@SuppressWarnings("unused")
	private static final int MAX_ENCRYPT_BLOCK = 117;
	/**
	 * 最大文件解密块
	 */
	@SuppressWarnings("unused")
	private static final int MAX_DECRYPT_BLOCK = 128;
	// 签名算法
	@SuppressWarnings("unused")
	private static final String SIGN_MD5WITHRSA = "MD5WithRSA";
	@SuppressWarnings("unused")
	private static final String SIGN_SHA1WithRSA = "SHA1WithRSA";
	private static final String SIGN_SHA256WithRSA = "SHA256WithRSA";
	private String _store;// 库文件名
	// private String _alias;// 秘钥对的 别名
	private Certificate _cert;// 证书文件
	private KeyStore _keyStore;// 秘钥库文件
	private char[] _pass;// 打开秘钥对的密码

	private final String _sign = SIGN_SHA256WithRSA;

	public static MyCert getInstance(String 存储路径) {
		return new MyCert(存储路径);
	}

	public static MyCert getInstance() {
		return new MyCert();
	}

	KeyFactory _keyf = null;

	private MyCert() {
		_algorithm = "RSA";
		try {
			_keyf = KeyFactory.getInstance(_algorithm);
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
	}

	private MyCert(String 存储路径) {
		_algorithm = "RSA";
		_store = 存储路径;
		try {
			_keyf = KeyFactory.getInstance(_algorithm);
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
	}

////////////////////////////创建key///////////////////////
	/**
	 * 默认生成1024位的密钥
	 * 
	 * @return
	 * 
	 */
	public MyCert createKey() {
		return createKey(_keysize);
	}

	/**
	 * 按照算法生成非对称秘钥
	 * 
	 * 长度可以设置
	 * 
	 * @param size= key长度
	 * @return
	 * 
	 */
	public MyCert createKey(int size) {
		KeyPairGenerator keyPairGen;
		try {
			keyPairGen = KeyPairGenerator.getInstance(_algorithm);

			SecureRandom random = new SecureRandom();
			keyPairGen.initialize(size, random);
			// 通过对象 KeyPairGenerator 获取对象KeyPair
			KeyPair keyPair = keyPairGen.generateKeyPair();
			// 通过对象 KeyPair 获取RSA公私钥对象RSAPublicKey RSAPrivateKey
			_pubkey = keyPair.getPublic();
			_prikey = keyPair.getPrivate();
		} catch (NoSuchAlgorithmException e) {

			e.printStackTrace();
		}
		return this;
	}

	/**
	 * 生成固定的公钥和私钥,没有太大意义
	 * 
	 * @param 随机数种子
	 * @return
	 * @throws NoSuchAlgorithmException
	 */
	public MyCert createKey(String 随机数种子) {

		try {
			KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(_algorithm);
			SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
			random.setSeed(随机数种子.getBytes());
			// SecureRandom random=new SecureRandom();
			keyPairGenerator.initialize(_keysize, random);// or 2048
			KeyPair keyPair = keyPairGenerator.generateKeyPair();
			_pubkey = keyPair.getPublic();
			_prikey = keyPair.getPrivate();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}

		return this;
	}
	////////////////////////////////////// end 创建key///////////////////

	///////////////////////////// 载入key start////////////////////
	/**
	 * 
	 * 
	 * <p>
	 * 配合 keytool 工具生成的keystore 文件 keytool -genkey -validity 36000 -alias
	 * www.zlex.org -keyalg RSA -keystore d:\zlex.keystore 从该文件中读取密钥
	 * </p>
	 * <p>
	 * PKCS#12是公钥加密标准，它规定了可包含所有私钥、公钥和证书。其以二进制格式存储，也称为 PFX
	 * 文件，在windows中可以直接导入到密钥区，注意，PKCS#12的密钥库保护密码同时也用于保护Key。 <br>
	 * BKS：来自BouncyCastle
	 * Provider，它使用的也是TripleDES来保护密钥库中的Key，它能够防止证书库被不小心修改（Keystore的keyentry改掉1个
	 * bit都会产生错误），BKS能够跟JKS互操作，读者可以用Keytool去TryTry。 <br>
	 * UBER：它比较特别，当密码是通过命令行提供的时候，它只能跟keytool交互。整个keystore是通过PBE/SHA1/Twofish加密，因此keystore能够防止被误改、察看以及校验。以前，Sun
	 * JDK(提供者为SUN)允许你在不提供密码的情况下直接加载一个Keystore，类似cacerts，UBER不允许这种情况。 *
	 * </p>
	 * 
	 * @param keyStorePath 密钥库存储路径
	 * @param password     密钥库密码
	 * @return
	 * @throws Exception
	 */
	public MyCert loadKeyStore(String keyStorePath, String password) throws Exception {
		FileInputStream in = new FileInputStream(keyStorePath);
		_pass = password.toCharArray();
		_keyStore = KeyStore.getInstance(_store.toString());
		_keyStore.load(in, _pass);
		in.close();
		return this;
	}

	/**
	 * 载入公钥私钥的目录
	 * 
	 * @param 目录
	 * @return
	 */
	public MyCert load(String 目录) {
		if (MyFile.exist(目录)) {
			File dir = MyFile.openDir(目录);
			String sdir = dir.getAbsolutePath() + "/";
			String sfile = sdir + "私钥.txt";
			loadPrikey(sfile);
			sfile = sdir + "公钥.txt";
			loadPubkey(sfile);
		} else {
			createKey().save(目录);
		}
		return this;
	}

	public MyCert loadBytes(String 目录) {
		if (MyFile.exist(目录)) {
			File dir = MyFile.openDir(目录);
			String sdir = dir.getAbsolutePath() + "/";
			String sfile = sdir + "私钥.key";
			loadPrikeyBytes(sfile);
			sfile = sdir + "公钥.key";
			loadPubkeyBytes(sfile);
		} else {
			createKey().saveBytes(目录);
		}
		return this;
	}

	/**
	 * 从公钥文件载入公钥
	 * 
	 * @param sfile
	 */
	public void loadPubkey(String sfile) {
		if (MyFile.exist(sfile)) {
			File file = new File(sfile);
			byte[] bs = MyFile.readAllBytes(file);
			String str = MyFun.bytes2U8str(bs);
			byte[] pubkey = MyBase64.decode(str);
			_pubkey = bytes2PubkeyX509(pubkey);
		}

	}

	/**
	 * 导入bytes 格式的 public key
	 * 
	 * @param sfile
	 */
	public void loadPubkeyBytes(String sfile) {
		if (MyFile.exist(sfile)) {
			File file = new File(sfile);
			byte[] bs = MyFile.readAllBytes(file);
			_pubkey = bytes2PubkeyX509(bs);
		}
	}

	/**
	 * 导入bytes 格式的 private key
	 * 
	 * @param sfile
	 */
	public void loadPrikeyBytes(String sfile) {
		if (MyFile.exist(sfile)) {
			File file = new File(sfile);
			byte[] bs = MyFile.readAllBytes(file);
			_prikey = bytes2PrikeyX509(bs);
		}
	}

	/**
	 * 从私钥文件载入私钥
	 * 
	 * @param sfile
	 */
	public void loadPrikey(String sfile) {
		if (MyFile.exist(sfile)) {
			File file = new File(sfile);
			byte[] bs = MyFile.readAllBytes(file);
			String str = MyFun.bytes2U8str(bs);
			byte[] prikey = MyBase64.decode(str);
			_prikey = bytes2PrikeyPKCS8(prikey);
		}

	}
	/////////////////////////////////////// end 载入key ////////////////////

	/////////////////////// 载入证书 start ////////////////////
	/**
	 * <p>
	 * 获得证书
	 * </p>
	 * 
	 * @param certificatePath 证书存储路径
	 * @return
	 * @throws Exception
	 */
	public MyCert loadX509Certificate(String certificatePath) throws Exception {
		CertificateFactory certificateFactory = CertificateFactory.getInstance(X509);
		FileInputStream in = new FileInputStream(certificatePath);
		_cert = certificateFactory.generateCertificate(in);
		_pubkey = _cert.getPublicKey();
		setCertProperties(_cert);
		in.close();
		return this;
	}

	/**
	 * 导入p12 证书
	 * 
	 * @param certificatePath
	 * @param pass
	 * @return
	 * @throws Exception
	 */
	public MyCert loadP12Certificate(String certificatePath, String pass) throws Exception {
		return loadCertificate("PKCS12", certificatePath, pass);
	}

	// certtype= {"PKCS12"}
	/**
	 * 导入各类型证书
	 * 
	 * @param certtype
	 * @param certificatePath
	 * @param pass
	 * @return
	 * @throws Exception
	 */
	public MyCert loadCertificate(String certtype, String certificatePath, String pass) throws Exception {
		char[] nPassword = pass.toCharArray();
		KeyStore ks = KeyStore.getInstance(certtype);
		FileInputStream fis = new FileInputStream(certificatePath);
		ks.load(fis, nPassword);
		fis.close();
		Enumeration<String> enum1 = ks.aliases();
		String keyAlias = null;
		if (enum1.hasMoreElements()) {
			keyAlias = (String) enum1.nextElement();
		}
		_prikey = (PrivateKey) ks.getKey(keyAlias, nPassword);
		_cert = ks.getCertificate(keyAlias);
		_pubkey = _cert.getPublicKey();
		setCertProperties(_cert);
		return this;
	}

	//////////////////////// end 载入证书 ////////////////////
	// 属性 start////////
	/**
	 * 获取私钥
	 * 
	 * @return
	 */
	public Key getPriKey() {
		return _prikey;
	}

	/**
	 * 获取公钥
	 * 
	 * @return
	 */
	public Key getPubKey() {
		return _pubkey;
	}

	public byte[] getPubBytes() {
		return _pubkey.getEncoded();
	}

	public byte[] getPriBytes() {
		return _prikey.getEncoded();
	}

	/**
	 * 得到base64格式私钥
	 * 
	 * @return
	 */
	public String getPriBase64() {
		return MyBase64.encode(_prikey.getEncoded());

	}

	/**
	 * 得到base64格式公钥
	 * 
	 * @return
	 */
	public String getPubBase64() {
		return MyBase64.encode(_pubkey.getEncoded());
	}

	/**
	 * 得到16进制私钥
	 * 
	 * @return
	 */
	public String getPriHex() {
		return MyByte.bytesToHex(_prikey.getEncoded());
	}

	/**
	 * 得到16进制公钥
	 * 
	 * @return
	 */
	public String getPublicHex() {
		return MyByte.bytesToHex(_pubkey.getEncoded());
	}
//////end 属性//////////////////

	/**
	 * 保存到文件
	 * 
	 * @param file
	 */
	public void save(String 目录) {
		File file = MyFile.openDir(目录);
		String dir = file.getAbsolutePath() + "/";
		String prifile = dir + "私钥.txt";
		File filepri = MyFile.openFile(prifile);
		MyFile.writeFile(filepri, getPriBase64(), MyCharset._UTF8);
		String pubfile = dir + "公钥.txt";
		File filepub = MyFile.openFile(pubfile);
		MyFile.writeFile(filepub, getPubBase64(), MyCharset._UTF8);
	}

	public void saveBytes(String 目录) {
		File file = MyFile.openDir(目录);
		String dir = file.getAbsolutePath() + "/";
		String prifile = dir + "私钥.key";
		File filepri = MyFile.openFile(prifile);
		MyFile.writeBytes(filepri, getPriBytes());
		String pubfile = dir + "公钥.key";
		File filepub = MyFile.openFile(pubfile);
		MyFile.writeBytes(filepub, getPubBytes());
	}

	/**
	 * 
	 * @param mode Cipher.ENCRYPT_MODE 加密 Cipher.DECRYPT_MODE 解密
	 * @param 密钥
	 * @return
	 * @throws Exception
	 */

	public MySecret do1选择(int mode, Key 密钥) {
		try {
			_cipher = Cipher.getInstance(_algorithm);
			_cipher.init(mode, 密钥);
		} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return this;
	}

	/**
	 * <p>
	 * 生成数据签名的流程 明文 》 摘要》 hash 》 加密 =》 签名 =》 base64 》发送
	 * </p>
	 */
	public String sign(String text) throws Exception {
		byte[] data = MyHash.getInsMD5().parse(text).getBytes();
		Signature sig = Signature.getInstance(_sign);
		// sig.initSign((PrivateKey) _key);
		sig.initSign(bytes2PrikeyPKCS8(_prikey.getEncoded()));
		sig.update(data);
		return MyBase64.encode(sig.sign());
	}

	/**
	 * 用公钥验证签名
	 * 
	 * @param data
	 * @param sign
	 * @return
	 * @throws Exception
	 */
	public boolean verifySign(byte[] data, byte[] sign) throws Exception {
		byte[] data1 =  MyHash.getInsMD5().parse(data).getBytes();
		Signature sig = Signature.getInstance(_sign);
		// sig.initVerify((PublicKey) _pubkey);
		sig.initVerify(bytes2PubkeyX509(_pubkey.getEncoded()));
		sig.update(data1);
		return sig.verify(sign);
	}

	//
	/**
	 * 把公钥 按x509 编码 ，在用自签名证书 验证签名时使用，其他地方用不到
	 * 
	 * @param key
	 * @return
	 * @throws Exception
	 */
	public PublicKey bytes2PubkeyX509(byte[] key) {
		try {

			X509EncodedKeySpec X509 = new X509EncodedKeySpec(key);
			return _keyf.generatePublic(X509);
		} catch (InvalidKeySpecException e) {
			e.printStackTrace();
			return null;
		}
	}

	public PrivateKey bytes2PrikeyX509(byte[] key) {
		try {
			X509EncodedKeySpec X509 = new X509EncodedKeySpec(key);
			return _keyf.generatePrivate(X509);
		} catch (InvalidKeySpecException e) {
			e.printStackTrace();
			return null;
		}

	}

	/**
	 * 把私钥 按PKCS8 编码 ，在签名时使用，其他地方用不到
	 * 
	 * @param key
	 * @return
	 * @throws Exception
	 */
	public PrivateKey bytes2PrikeyPKCS8(byte[] key) {
		try {
			PKCS8EncodedKeySpec pkcs8 = new PKCS8EncodedKeySpec(key);
			return _keyf.generatePrivate(pkcs8);
		} catch (InvalidKeySpecException e) {
			e.printStackTrace();
			return null;
		}
	}

	public PublicKey bytes2PubkeyPKCS8(byte[] key) {
		try {
			PKCS8EncodedKeySpec pkcs8 = new PKCS8EncodedKeySpec(key);
			return _keyf.generatePublic(pkcs8);
		} catch (InvalidKeySpecException e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * <p>
	 * 验证证书是否过期，无效，是否是服务端签发
	 * </p>
	 * 
	 * @param date        日期
	 * @param certificate 证书
	 * @return
	 */
	public boolean verifyCert(Date date, Certificate certificate) {
		boolean isValid = true;
		try {
			X509Certificate ct = (X509Certificate) certificate;
			ct.checkValidity(date);
		} catch (Exception e) {
			isValid = false;
		}
		return isValid;
	}

	///////////////////// cert 属性、、、、、、、、、、、、、、、、、、
	protected String title;// 版本
	protected String version;// 版本
	protected String start;// 开始
	protected String end;// 截止
	protected String type;// 类型
	protected String dn;// 发行方
	protected String no;// 序号
	protected String sigAlg;// 签名算法
	protected byte[] signature;// 签名内容
	protected String certInfo;// 证书内容

	public void setCertProperties(Certificate cert) {
		if (cert instanceof X509Certificate) {
			X509Certificate ct = (X509Certificate) cert;
			this.title = ct.getSubjectDN().getName();
			this.version = Integer.toString(ct.getVersion());
			this.start = MyFun.date2Str(ct.getNotBefore());// 得到开始有效日期
			this.end = MyFun.date2Str(ct.getNotAfter());// 得到截止日期
			this.type = ct.getType();
			this.dn = ct.getIssuerDN().getName();// 得到发行者名
			this.no = ct.getSerialNumber().toString(16);// 得到序列号
			this.sigAlg = ct.getSigAlgName();// 得到签名算法
			this.certInfo = ct.toString();
			this.signature = ct.getSignature();
			this._algorithm = ct.getPublicKey().getAlgorithm();/* 得到公钥算法 */
		}
	}

	public String getCA证书() {
		return certInfo;
	}

	public byte[] getCA签名() {
		return signature;
	}

	public String getCA十六进制签名() {
		return MyFun.encodeHex(signature);
	}

	public String getCA标题() {
		return title;
	}

	public String getCA版本() {
		return version;
	}

	public String getCA开始时间() {
		return start;
	}

	public String getCA截止时间() {
		return end;
	}

	public String getCA类型() {
		return type;
	}

	public String getCA发行者() {
		return dn;
	}

	/**
	 * 序号
	 * 
	 * @return
	 */
	public String getCA序号() {
		return no;
	}

	public String getCA签名算法() {
		return sigAlg;
	}

}
